package algo_lignes_sites.data.junction;

import algo_lignes_sites.data.path.A_Path;
import algo_lignes_sites.ihm.couche.CoucheContrainteGeomNode;
import algo_lignes_sites.params.objects.contraintesgeom.ContrainteGeomNode;
import commons.data.Attributes;
import commons.data.EchelleGeneralization;
import commons.data.I_Searchable;
import commons.data.Zones;
import commons.drawing.I_Drawable;
import commons.errors.ErrorsHandler;
import commons.ihm.ParametresAffichageCarto;
import commons.ihm.ParametresAffichageCarto.ModeAffichage;
import commons.ihm.ParametresAffichageCarto.TypeAffichage;
import commons.io.in.Point_in;
import commons.logs.Logger;
import commons.spatialindex.I_SpatialIndexable;
import commons.utils.UtilsCalcul;
import commons.utils.primitive_geom.Point_d;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.regex.Pattern;

public abstract class A_Junction extends Point_d implements I_SpatialIndexable, I_Drawable, I_Searchable, Comparable<A_Junction>{

	// utilise pour se souvenir de trio de junction deja teste dans la simplification topologique
	static private int currentID;
	static{currentID=0;}
	public int id;
	
	// TODO a mettre dans les paramétrages généraux
	private static final float LARGEUR_TOPO_PT_STD = 3;
	private static final float LARGEUR_TOPO_PT_CONNECTx = 5f;
	private static final float LARGEUR_TOPO_PT_INTERSECT = 5;
	private static final float LARGEUR_TOPO_PT_NODE = 8;

	// Remarques :
	//  CONNECT_2 = exactement 2 path différents connectés (il peut y avoir 3 segment connectés si un des path reviens sur lui-même ...
	//  CONNECT_3 = >=3 path différents connectés
	public enum TypeJunction {NOT_SET, STD, CONNECT_2, CONNECT_3, INTERSECT, NODE}

	protected TypeJunction typeJunction;
	public TypeJunction getTypeJunction() {return typeJunction;}

	public void setTypeJunction (TypeJunction typeJunction) {
		assert (typeJunction != TypeJunction.NODE) : "node = uniquement via constructeur spécial";
		this.typeJunction = typeJunction;
	}
	public boolean isNode() {return typeJunction==TypeJunction.NODE;}
	public boolean isIntersect() {return typeJunction==TypeJunction.INTERSECT;}
	public boolean isIntersectOrConnect3() {return typeJunction==TypeJunction.INTERSECT || typeJunction==TypeJunction.CONNECT_3;}
	public boolean isPtMultiple() {return typeJunction==TypeJunction.NODE || typeJunction==TypeJunction.INTERSECT || typeJunction==TypeJunction.CONNECT_3;}
	public boolean isConnect2() {return typeJunction==TypeJunction.CONNECT_2;}
	public boolean isConnect3() {return typeJunction==TypeJunction.CONNECT_3;}
	public boolean isStd() {return typeJunction==TypeJunction.STD;}
	public boolean isNotSet() {return typeJunction==TypeJunction.NOT_SET;}

	// Met à jour le type de la junction suivant les path qui la contiennent
	// Il faut donc avoir au préalable mis à jour les path Contenant
	// Attention : un node restera un node !
	public void majTypeJunction() {
		//assert (!isNode());
		//assert (nbPathContenant>0) : "Junction isolée";

		if (isNode())
			return;

		if (nbPathContenant == 0)
			setTypeJunction(A_Junction.TypeJunction.NOT_SET);
		else if(nbPathContenant == 1)
			setTypeJunction(A_Junction.TypeJunction.STD);
		else if (nbPathContenant==2)
			setTypeJunction(A_Junction.TypeJunction.CONNECT_2);
		else
			setTypeJunction(A_Junction.TypeJunction.CONNECT_3);

		//Si 2 path ou plus, ca pourrait etre une intersect si l'un des path est traversant
		if (nbPathContenant >= 2)
			for (A_Path path : getVPathContenant())
				// Le test :  if (!jG.isExtremite(path)) est faux puisque le path peut partir de l'intersect reboucler dessus, il sera alors traversant
				// Il faut tester explicitement si le path est traversant
				//if (!jG.isExtremite(path)) {
				if (isPathTraversant(path)) {
					setTypeJunction(A_Junction.TypeJunction.INTERSECT);
					break;
				}
	}

	// Variable utilisée uniquement pour les Nodes
	public NodeData nodeData;
	
	// Pour stocker des infos de debug
	String infoDebug="";
	public void addInfoDebug(String info) {
		if (Logger.isDebugLevel())
			infoDebug += " - " + info + "\n";
	}

	//*********************  DISSYMETRIES  *************************************
	private boolean bHasDissymetrie;

	// En fin de pretraitement, Data_ls appele cette fonction qui precalcule
	// si la junction a une dissymétrie, ce qui accelerera la suite des traitements
	public void computeBHasDyssimetrie() {
		bHasDissymetrie = false;
		for (int i=0 ; i<nbPathContenant ; i++) {
			A_Path p = getPathContenantNum(i);
			if (p.hasDissymetrie()) {
				bHasDissymetrie = true;
				return;
			}
		}
	}

	public boolean hasDissymetrie() {return bHasDissymetrie;}


	//**************************************************************************

	// Pour un NODE, il s'agit de son diametre, pour un STD, c'est l'épaisseur de son path
	//  pour un point multiplie c'est le max des épaisseurs de ses path
	public float diametre_reel_m; // en mètre, dépend de l'échelle voulue pour la carte et du diametre papier
	public float espaceAutour_reel_m; // en mètre, dépend de l'échelle voulue pour la carte et de l'épaisseur papier
	public float epaisseurDissymetrie_reel_m;


	public A_Junction(double x, double y) {
		super(x, y);
	    id = currentID++;
		typeJunction = TypeJunction.NOT_SET;

		// Paramètre géométriques
		//  ces infos seront renseignées via majContrainteGeom_echelleCouranteChanged()
		diametre_reel_m = 0;
		espaceAutour_reel_m = 0;
		epaisseurDissymetrie_reel_m = 0;
	}
	public A_Junction(double x, double y, TypeJunction typeJunction) {
		this(x, y);
		this.typeJunction = typeJunction;
		assert(typeJunction != TypeJunction.NODE); // pour ce type de junction un constructeur spécial est prévu
	}
	public A_Junction(double x, double y, TypeJunction typeJunction, A_Path path) {
		this(x, y, typeJunction);
		addPathContenant(path);
	}

	public A_Junction(Point_d pt, TypeJunction typeJunction) {
		this(pt.x, pt.y, typeJunction);
	}
	public A_Junction(Point_d pt, TypeJunction typeJunction, A_Path path) {
		this(pt.x, pt.y, typeJunction);
		addPathContenant(path);
	}
	
	// Création de junction de type node
	// vNodeInitFusionne : algo >= v22 : si ce node est temporaire car fusion d'autres node durant le traitement
	public A_Junction (double x, double y, Attributes attributes, Zones zones, ContrainteGeomNode cgn, EchelleGeneralization eg, Color color, ArrayList<Point_in> vNodeInitFusionne) {
		this(x, y);

		this.typeJunction = TypeJunction.NODE;
		this.nodeData = new NodeData(attributes, zones, cgn, eg, color, vNodeInitFusionne);
	}


	// Distance entre this et j sur les coordonnées REF
	abstract protected double distTo_ref(A_Junction j);

	//**************************************************************************
	// TOPOLOGIE (topologie figée une fois l'initialisaton des données terminée)
	//**************************************************************************

	// Lien vers les path connectés si point commun à plusieurs path
	// TODO_OK PERF : mettre dans une variable simple si un seul path concerné !!
	// TODO_KO PERF-MEM : mettre dans un tableau simple

	protected int nbPathContenant; // nb de path DIFFERENTS passant par this
	private ArrayList<A_Path> vPathContenant; //on ne le crée que si nbPathContenant > 1 (ie. hors STD)
	protected A_Path mainPathContenant;
	private void setMainPathContenant() {if (vPathContenant.size()>0) mainPathContenant = vPathContenant.get(0);}

	public int getNbPathContenant() {return nbPathContenant;}
	public A_Path getMainPathContenant() {return mainPathContenant;}
	public A_Path getPathContenantNum (int index) {
//		if (index==1 && vPathContenant!=null && vPathContenant.size()==1)
//			index=index;
		if (index==0)
			return mainPathContenant;
		return vPathContenant.get(index);
	}
	
	// L'appelant ne doit pas modifier le contenu de ce vecteur !!
	public ArrayList<A_Path> getVPathContenant() {
		if (vPathContenant==null) {
			// on a forcément nbPathContenant==0 ou 1
			assert nbPathContenant<=1;
			ArrayList<A_Path> vRes = new ArrayList<A_Path>(nbPathContenant);
			if (nbPathContenant==1)
				vRes.add(mainPathContenant);
			return vRes;
		} else
			return vPathContenant;
	}
	public ArrayList<A_Path> getVPathContenant_traversant() {
		ArrayList<A_Path> vRes = new ArrayList<A_Path>();

		// les path non traversant sont une notion propre aux intersect
		if (!this.isIntersect())
			return vRes;

		for (A_Path path : this.getVPathContenant())
			if (isPathTraversant(path))
				vRes.add(path);
		return vRes;
	}
	public ArrayList<A_Path> getVPathContenant_nonTraversant() {
		ArrayList<A_Path> vRes = new ArrayList<A_Path>();
		for (A_Path path : this.getVPathContenant())
			if (!isPathTraversant(path))
				vRes.add(path);
		return vRes;
	}


	public boolean isPathTraversant(A_Path path) {
		// Le test :  if (path.from()!=this && path.to()!=this) est faux puisque le path peut être traversant et reboucler sur l'intersect !
		// il faut tester explicitement si le path référence this quelque part entre ses 2 extrémités
		ArrayList<A_Junction> vJ = path.getVJunction();
		int nbJ = vJ.size();
		for (int i=1 ; i<nbJ-1 ; i++) {
			if (vJ.get(i) == this)
				return true;
		}
		return false;
	}

	
	final public void addPathContenant (A_Path path) {
		// dans le cas de la construction des Junction DER, ce test ne fonctionne pas
		//  on assigne aux junctions DER le path avant que le path ne soit rempli ...
		if ((isNode() || isConnect3()) && this instanceof JunctionREF)
			assert (isExtremite(path));

		if (nbPathContenant==0) {
			mainPathContenant = path;
			nbPathContenant = 1;
		}
		else if(nbPathContenant == 1) {
			if (vPathContenant == null) {
				vPathContenant = new ArrayList<A_Path>(5);
				vPathContenant.add(mainPathContenant);
			}
			if (path != mainPathContenant) {
				vPathContenant.add(path);
				nbPathContenant = 2;
			}
		}
		else { // nbPathContenant > 1

			// recherche si le path a déjà été connecté auparavant
			// cf. des path peuvent boucler sur eux-mêmes !
			for (A_Path p : vPathContenant)
				if (p==path)
					return;
			vPathContenant.add(path);
			nbPathContenant++;
			setMainPathContenant();
		}
	}

	public void removePathContenant (A_Path path) {
		if (nbPathContenant==0)
			return;

		if (nbPathContenant==1) {
			if (mainPathContenant==path) {
				if (vPathContenant!=null)
					vPathContenant.clear();
				mainPathContenant = null;
				nbPathContenant = 0;
			}
		} else { // nbPathContenant > 1
			for (int i=0; i<vPathContenant.size() ; i++) {
				if (path == vPathContenant.get(i)) {
					vPathContenant.remove(i);
					setMainPathContenant();
					nbPathContenant--;
					return;
				}
			}
		}
	}

	// Largeur max des path contenant this (!!! = LARGEUR FINALE, à l'échelle la plus petite pour le path en question)
	public double getLargestPathContenant_onlyLine() {
		if (nbPathContenant==1)
			return mainPathContenant.getFinalWidth_onlyLine();

		if (nbPathContenant==0)
			return 0;

		// nbPathContenant > 1
		double widthMax = 0;
		for (A_Path p : vPathContenant) {
			double widthPath = p.getFinalWidth_onlyLine();
			if (widthPath > widthMax)
				widthMax = widthPath;
		}
		return widthMax;
	}

	public double getLargestPathContenant_withDissy() {
		if (nbPathContenant==1)
			return mainPathContenant.getFinalWidth_withDissy();

		if (nbPathContenant==0)
			return 0;

		// nbPathContenant > 1
		double widthMax = 0;
		for (A_Path p : vPathContenant) {
			double widthPath = p.getFinalWidth_withDissy();
			if (widthPath > widthMax)
				widthMax = widthPath;
		}
		return widthMax;
	}

	public double getLargestPathContenant_withDissy_withEspace() {
		if (nbPathContenant==1)
			return mainPathContenant.getFinalWidth_withDissy_withEspace();

		if (nbPathContenant==0)
			return 0;

		// nbPathContenant > 1
		double widthMax = 0;
		for (A_Path p : vPathContenant) {
			double widthPath = p.getFinalWidth_withDissy_withEspace();
			if (widthPath > widthMax)
				widthMax = widthPath;
		}
		return widthMax;
	}


	public boolean faitPartieDuPath(A_Path path) {
		if (nbPathContenant == 1)
			return path==mainPathContenant;

		if (nbPathContenant == 0) // cas des nodes isolés
			return false;

		for (int i=0; i<nbPathContenant; i++) {
			A_Path p=vPathContenant.get(i);
			if (p==path)
				return true;
		}

		return false;
	}


	public boolean faitPartieDuMemePathOuSuperPath(A_Path path) {
		if (nbPathContenant == 0) return false; // cas des nodes isolés
		if (mainPathContenant==path || mainPathContenant.superPath == path.superPath)
			return true;

		// pas de succès avec le premier path contenant (mainPathContenant)
		// si plus de path contenant, on poursuit avec eux
		if (nbPathContenant > 1) {
			for (int i=1; i<nbPathContenant; i++) {
				A_Path p=vPathContenant.get(i);
				if (p==path || p.superPath == path.superPath)
					return true;
			}
		}
		return false;
	}



	//**************************************************************************
	//**************************************************************************

	public boolean isExtremite (A_Path path) {
		//cf. ce test fonctionne car il n'y a pas de doublon dans les junctions
		// (sinon, il faudrait tester l'égalité au niveau des coordonnées)
		return this==path.from() || this==path.to();
	}

	
	// Renvoie le rayon qu'aura le node en fin de traitement
	public double getRayonNodeFinal() {
		assert (isNode());
		return 0.5 * nodeData.contrainteGeom.diametre_papier_mm/1000.0 * nodeData.echelleGeneralization.echelle_inv;
	}
	
	public double getRayonNodeFinal_avecEspaceAutour() {
		assert (isNode());
		return 0.5 * (nodeData.contrainteGeom.diametre_papier_mm + nodeData.contrainteGeom.espaceAutour_papier_mm)/1000.0 * nodeData.echelleGeneralization.echelle_inv;
	}

	
	// Attention : pour les nodes, on n'a pas besoin des epaisseurs des path connected
	//   -> les nodes ont leur diametre propres définit par leur contrainte geometrique
	// Mais pour les autres types de junction, le diametre dépend des path qui sont connectés
	//  il faut donc avoir calculé les epaisseurs des path auparavant !
	// Le seul appelant, Data_ls, prend soin de cela
	public void majContrainteGeom_echelleCouranteChanged(double echelleCarteCourante_inv) {
		if (mainPathContenant==null && !isNode()) {
			Logger.log_ERROR("++++++++++ Junction isolée !! " + this.getInfoDetaillee() + "++++++++++++++++");
			if (ErrorsHandler.assertsEnabled) assert(false);
			return;
		}
		switch (typeJunction) {
			case NODE:
				// On ne "grossit" pas le node au dela de l'échelle max de sa Regle de Generalisation
				if (echelleCarteCourante_inv > nodeData.echelleGeneralization.echelle_inv)
					echelleCarteCourante_inv = nodeData.echelleGeneralization.echelle_inv;
				diametre_reel_m = (float) (nodeData.contrainteGeom.diametre_papier_mm * echelleCarteCourante_inv / 1000.0);
				espaceAutour_reel_m = (float) (nodeData.contrainteGeom.espaceAutour_papier_mm * echelleCarteCourante_inv / 1000.0);
				epaisseurDissymetrie_reel_m = 0;
				break;

			case STD:
				diametre_reel_m = mainPathContenant.epaisseurTrait_reel_m;
				espaceAutour_reel_m = mainPathContenant.espaceAutour_reel_m;
				epaisseurDissymetrie_reel_m = mainPathContenant.epaisseurDissymetrie_reel_m;
				break;

			case CONNECT_2:
			case CONNECT_3:
			case INTERSECT:
				// on prend les dimensions max des différents path qui contiennent cette junction
				diametre_reel_m = mainPathContenant.epaisseurTrait_reel_m;
				espaceAutour_reel_m = mainPathContenant.espaceAutour_reel_m;
				for (int i=1; i<nbPathContenant; i++) {
					A_Path path=getPathContenantNum(i);
					diametre_reel_m = Math.max(diametre_reel_m, path.epaisseurTrait_reel_m);
					espaceAutour_reel_m = Math.max(espaceAutour_reel_m, path.espaceAutour_reel_m);
					epaisseurDissymetrie_reel_m = Math.max(epaisseurDissymetrie_reel_m , path.epaisseurDissymetrie_reel_m);
				}
				break;

			default:
				assert(false) : "type de junction inconnue";
		}
	}


	public String getType_asString() {
		if (typeJunction==TypeJunction.STD) return "Standard";
	    if (typeJunction==TypeJunction.CONNECT_2) return "Connect2";
		if (typeJunction==TypeJunction.CONNECT_3) return "Connect3";
		if (typeJunction==TypeJunction.INTERSECT) return "Intersect";
		if (typeJunction==TypeJunction.NODE) return "Node";
		assert false : "type de point inconnu";
		return "";
	}

	//**************************************************************************
	//  I_Searchable
	//**************************************************************************
	@Override
	public boolean matchSearch(Pattern pattern) {
		if (!isNode()) return false;
		int nbAttrib = nodeData.attributes.getModel().getNumberOfField();
		for (int i=0; i<nbAttrib ; i++) {
			 if (pattern.matcher(nodeData.attributes.getValue(i).toUpperCase()).matches())
				 return true;
		}
		return false;
	}


	//**************************************************************************
	//  Comparable
	//**************************************************************************

	@Override
	public int compareTo(A_Junction j2) {

		// Si c'est 2 nodes
		if (this.isNode() && j2.isNode()) {

			Comparable val1 = nodeData.valueToCompare_forDisplay;
			Comparable val2 = j2.nodeData.valueToCompare_forDisplay;

			if (NodeData.bBiggestOnTop)
				return val1.compareTo(val2);
			else
				return - val1.compareTo(val2);

		} else if (this.isNode() && !j2.isNode()) {
			return 1;
		} else if (!this.isNode() && j2.isNode()) {
			return -1;
		} else {
			return 0;
		}
	}


	//**************************************************************************
	//  I_Drawable
	//**************************************************************************

	@Override
	public void draw(Graphics2D g, ParametresAffichageCarto pac) {
		Color col;
		Shape shape = getShape(pac);

		// Si node en mode highlight topologique, on affihe les contraintes geometrique (dimension, zones d'influence, et param topo : ZS et ZC
		if (isNode() && pac.modeAffichage == ModeAffichage.HIGHLIGHT && pac.typeAffichage == TypeAffichage.TOPO) {
			ArrayList<I_Drawable> vDrawable = new ArrayList<I_Drawable>();
			CoucheContrainteGeomNode.addDrawables(this, CoucheContrainteGeomNode.TypeContraineGeom.ALL, vDrawable);
			for (I_Drawable d : vDrawable)
				d.draw(g, pac);
		}
			
		
		// Intérieur du rond
		col = getFillColor(pac.typeAffichage);
		if ((col != null)) {
			g.setColor(col);
			g.fill(shape);
		}

		// Bordure du rond
		col = getOutlineColor(pac);
		if ((col != null)) {
			g.setColor(col);
			
			// pourtour plus épais si mode sélection
			if (pac.modeAffichage == ModeAffichage.SELECTION || pac.modeAffichage == ModeAffichage.HIGHLIGHT)
				g.setStroke(pac.getBasicStroke2());
			else
				g.setStroke(pac.getBasicStroke1());
			
			g.draw(shape);
		}

	}
	

	static private Ellipse2D.Float shEllipse = new Ellipse2D.Float();
	static private Rectangle2D.Float shRect = new Rectangle2D.Float();
	private Shape getShape(ParametresAffichageCarto pac) {
		Shape shape=null;
		// TODO_KO OPTIM : ne pas recréer des shape a chaque fois ?
		if (pac.typeAffichage == TypeAffichage.TOPO) {
			switch (typeJunction) {
				case STD:
					// rond petit
					shEllipse.x = pac.x2X(x) - LARGEUR_TOPO_PT_STD/2;
					shEllipse.y = pac.y2Y(y) - LARGEUR_TOPO_PT_STD/2;
					shEllipse.width  = LARGEUR_TOPO_PT_STD;
					shEllipse.height = LARGEUR_TOPO_PT_STD;
					shape = shEllipse;
					break;

				case CONNECT_2:
				case CONNECT_3:
					// carré moyen (c'est au niveau de la FillColor qu'ils se différentieront
					shRect.x = pac.x2X(x) - LARGEUR_TOPO_PT_CONNECTx/2;
					shRect.y = pac.y2Y(y) - LARGEUR_TOPO_PT_CONNECTx/2;
					shRect.width  = LARGEUR_TOPO_PT_CONNECTx;
					shRect.height = LARGEUR_TOPO_PT_CONNECTx;
					shape = shRect;
					break;
				case INTERSECT:
					// rond moyen
					shEllipse.x = pac.x2X(x) - LARGEUR_TOPO_PT_INTERSECT/2;
					shEllipse.y = pac.y2Y(y) - LARGEUR_TOPO_PT_INTERSECT/2;
					shEllipse.width  = LARGEUR_TOPO_PT_INTERSECT;
					shEllipse.height = LARGEUR_TOPO_PT_INTERSECT;
					shape = shEllipse;
					break;
				case NODE:
					// rond gros
					shEllipse.x = pac.x2X(x) - LARGEUR_TOPO_PT_NODE/2;
					shEllipse.y = pac.y2Y(y) - LARGEUR_TOPO_PT_NODE/2;
					shEllipse.width  = LARGEUR_TOPO_PT_NODE;
					shEllipse.height = LARGEUR_TOPO_PT_NODE;
					shape = shEllipse;
					break;

				default:
					assert false : "Type de point non pris en charge pour l'affichage : " + typeJunction;
			}
		} else if (pac.typeAffichage == TypeAffichage.PAPIER) {
			if (typeJunction == TypeJunction.NODE) {
				float diametre_px = pac.lg2LG(diametre_reel_m);
				shEllipse.x = pac.x2X(x) - diametre_px/2;
				shEllipse.y = pac.y2Y(y) - diametre_px/2;
				shEllipse.width  = diametre_px;
				shEllipse.height = diametre_px;
				shape = shEllipse;
			} else {
				shape = null;
			}

		} else {
			assert false : "Type d'affichage non pris en charge pour l'affichage : " + pac.typeAffichage;
		}
		return shape;
	}

	private Color getOutlineColor(ParametresAffichageCarto pac) {
		Color normalColor = pac.typeAffichage == TypeAffichage.TOPO ? Color.BLACK : Color.WHITE;
		
		// si node temporaire, on met une couleur speciale
		if (isNode() && nodeData.isNodeTemporaireFusionne())
			normalColor = Color.BLACK;
					
		return pac.getColorSelonModeAffichage(normalColor);
	}

	private Color getFillColor(TypeAffichage typeAffichage) {

		if (typeAffichage == TypeAffichage.TOPO) {
			switch (typeJunction) {
				case STD:
				case CONNECT_2:
					return Color.BLACK;
				case CONNECT_3:
					return Color.CYAN;
				case INTERSECT:
					return Color.PINK;
				case NODE:
							
					// si node temporaire, on met une couleur speciale
					if (nodeData.isNodeTemporaireFusionne())
						return Color.WHITE;
					else
						return nodeData.color;

				default:
					assert false : "Type de point non pris en charge pour l'affichage " + typeJunction;
			}
		} else  if (typeAffichage == TypeAffichage.PAPIER) {
			// on ne dessine que des nodes
			assert(isNode());
			
			if (nodeData.isNodeTemporaireFusionne())
				return Color.WHITE;
			else
				return nodeData.color;
			
		} else {
			assert false : "Type d'affichage non pris en charge pour l'affichage " + typeAffichage;
		}
		return null;
	}


	@Override
	public String getInfoBulle() {
		if (isNode())
			return nodeData.attributes.getValue(NodeData.champLibelleNode);
		else
			return getType_asString();
	}

	@Override
	public String getInfoDetaillee() {
		String res = "";

		res += super.toString() + "\n\n";

		res += "Type : " + getType_asString() + "\n\n";

		//Si node
		if (isNode()) {
			res += "Contrainte Geom : " + nodeData.contrainteGeom + "\n";
			res += nodeData.zones.toString() + "\n";
			res += "Règle généralisation : echelle -> " + nodeData.echelleGeneralization.echelle_inv + "\n\n";
		}

		res += "Nb path connected  : " + getNbPathContenant() + "\n";

		res += "\n";
		if (isNode()) {
			if (nodeData.isNodeTemporaireFusionne()) {
				res += "NODE TEMPORAIRE FUSIONNE \n";
				res += " nb nodes init : " + nodeData.vNodeInitFusionne.size() + "\n";
			}
			
			res += "Complexité node : " + UtilsCalcul.round2(nodeData.complexite_apresSimplif) + "\n";
			res += " -> réduction d'influence : " + UtilsCalcul.round2(nodeData.factReductionInfluenceNode) + "\n";
		}
		
		res += "\n";
		res += getInfoDetaille_specific();

		res += "\n";
		if (isNode()) {
			res += nodeData.attributes.toString();
			// catégories du node
			res += "Catégories :\n";
			res += nodeData.attributes.getListCategoriesToString();
		}
		

		
		res += "\n";
		if (Logger.isDebugLevel() && !infoDebug.isEmpty())
			res += "***** INFOS SPECIFIQUES *****\n" + infoDebug;

		return res;
	}

	// complément d'information spécifique aux dérivées
	abstract String getInfoDetaille_specific();

	@Override
	public String toString() {return getInfoBulle();}

}
